package com.nice.common.memory;

import java.util.concurrent.atomic.AtomicInteger;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.util.HashMap;

public final class BufferPool {

    /**
     * 默认申请20M大小的内存池
     */
    private static final int DEFAULT_POOL_SIZE = 1024 * 1024 * 20;

    /**
     * 每个block的大小
     */
    private static final int EACH_BLOCK_SZIE = 1024 * 8;

    /**
     * 单例对象
     */
    private static BufferPool INSTANCE;

    /**
     * 按pool->blocks缓存
     */
    private HashMap<byte[], BufferBlock[]> pools;

    /**
     * buffer申请的次数
     * 
     */
    private AtomicInteger allocateTimes = new AtomicInteger();

    /**
     * 回收次数
     */
    private AtomicInteger recoveryTimes = new AtomicInteger();

    private Constructor<?> constructor;

    private Object[] initArgs;

    /**
     * 
     * 基于DCL的单例获取模式,请在jdk5.0及以上环境使用
     * 
     * @param poolSize
     * @param allocateInHeapIfPoolIsFull
     * @return
     * 
     */
    public static BufferPool getInstance(int poolSize) {
        if (INSTANCE == null) {
            synchronized (BufferPool.class) {
                if (INSTANCE == null) {
                    INSTANCE = new BufferPool(poolSize);
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 
     * @return
     *         DEFAULT_POOL_SIZE大小的内存池
     */
    public static BufferPool getInstance() {
        return getInstance(DEFAULT_POOL_SIZE);
    }

    /**
     * 
     * 申请指定大小的ByteBuffer
     * 
     * @param size
     * @return
     */
    public ByteBuffer allocateBytebuf(int size) {
        allocateTimes.getAndIncrement();
        for (byte[] pool : pools.keySet()) {
            ByteBuffer buf = findFreeBlockCreateBuffer(pool, size);
            if (buf != null)
                return buf;
        }
        byte[] pool = initBlock();
        return findFreeBlockCreateBuffer(pool, size);
    }

    /**
     * 回收申请的内存,如果参数不是池中申请的,不会有任何影响
     * 
     * @param buf
     */
    public void recovery(ByteBuffer buf) {
        if (buf == null)
            return;
        BufferBlock[] blocks = pools.get(buf.array());
        if (blocks == null)
            return;
        int arrayOffset = buf.arrayOffset();
        int blockIndex = arrayOffset / EACH_BLOCK_SZIE;
        BufferBlock bufferBlock = blocks[blockIndex];
        bufferBlock.recovery(buf, blockIndex);
        recoveryTimes.getAndIncrement();
    }

    /**
     * 
     * 清空内存池
     */
    public synchronized void clear() {
        pools.clear();
        pools = null;
        INSTANCE = null;
    }

    public AtomicInteger getAllocateTimes() {
        return allocateTimes;
    }

    public AtomicInteger getRecoveryTimes() {
        return recoveryTimes;
    }

    /**
     * 私有构造器
     * 
     * @param initSize
     * @param allocateInHeapIfPoolIsFull
     */
    private BufferPool(int initSize) {
        try {
            // byte[] buf,int mark, int pos, int lim, int cap,int off
            Object[] args = { null, -1, 0, 0, 0, 0 };
            Class<?>[] pars = { byte[].class, int.class, int.class, int.class, int.class, int.class };
            Class<?> cls = Class.forName("java.nio.HeapByteBuffer");

            this.pools = new HashMap<byte[], BufferBlock[]>();
            this.constructor = cls.getDeclaredConstructor(pars);
            this.constructor.setAccessible(true);
            this.initArgs = args;
            this.initBlock();

        } catch (Exception e) {
            throw new PoolBufferException("can't init buffer pool,", e);
        }
    }

    /**
     * 找到一个空闲的block来创建比亚特buffer
     * 
     * @param pool
     * @param size
     * @return
     */
    private ByteBuffer findFreeBlockCreateBuffer(byte[] pool, int size) {
        BufferBlock[] arrayList = pools.get(pool);
        for (BufferBlock bufferBlock : arrayList) {
            if (bufferBlock.canAllocate(size)) {
                return bufferBlock.createBuffer(size, constructor, initArgs);
            }
        }
        return null;
    }

    private byte[] initBlock() {
        byte[] pool = new byte[DEFAULT_POOL_SIZE];
        int remain = pool.length % EACH_BLOCK_SZIE;
        int blockCount = pool.length / EACH_BLOCK_SZIE;
        int realCount = (remain > 0) ? blockCount + 1 : blockCount;

        int i = 0;
        int j = 0;
        BufferBlock[] blocks = new BufferBlock[realCount];
        while (j < realCount) {
            blocks[j++] = new BufferBlock(pool, EACH_BLOCK_SZIE, i);
            i += EACH_BLOCK_SZIE;
        }

        if (remain > 0) {
            blocks[j] = new BufferBlock(pool, remain, i);
        }

        pools.put(pool, blocks);
        System.out.println(String.format("allocate[%s]bytes,spit to[%s]blocks", DEFAULT_POOL_SIZE, realCount));
        return pool;
    }
}
